12장 immer 사용해서 불변성 유지하기
코드 폼에서 아이디/이름 입력받아서 리스트 보여주는 컴포넌트
const App = () => {
const nextId = useRef(1);
const [form, setForm] = useState({ name: "", username: "" });
const [data, setData] = useState({
array: [],
uselessValue: null,
});
// input 수정을 위한 함수
const onChange = useCallback(
(e) => {
const { name, value } = e.target;
setForm({
...form,
[name]: [value],
});
},
[form]
);
// form 등록을 위한 함수
const onSubmit = useCallback(
(e) => {
e.preventDefault();
const info = {
id: nextId.current,
name: form.name,
username: form.username,
};
// array에 새 항목 등록
setData({
...data,
array: data.array.concat(info),
});
//form 초기화
setForm({
name: "",
username: "",
});
nextId.current += 1;
},
[data, form.name, form.username]
);
const onRemove = useCallback(
(id) => {
setData({
...data,
array: data.array.filter((info) => info.id !== id),
});
},
[data]
);
return (
<div>
<form onSubmit={onSubmit}>
<input
name="username"
placeholder="아이디"
value={form.username}
onChange={onChange}
/>
<input
name="name"
placeholder="이름"
value={form.name}
onChange={onChange}
/>
<button type="submit">등록</button>
</form>
<div>
<ul>
{data.array.map((info) => (
<li key={info.id} onClick={() => onRemove(info.id)}>
{info.username} ({info.name})
</li>
))}
</ul>
</div>
</div>
);
};
전개 연산자 or 배열 내장 함수 사용해서 객체를 새롭게 만들어서 불변성을 유지한다.
→ 상태가 복잡해지면 귀찮아질 수 있다.
immer 사용
immer 의 produce
함수 사용해서 불변성 유지
produce
함수 2가지 인자를 받음
- 수정하고 싶은 state
- state를 어떻게 업데이트 할 지 정의하는 함수(콜백 함수)
콜백 함수에 state가 인자로 넘겨진다.
import produce from 'immer';
const nextState = produce(originalState, draft => {
// 바꾸고 싶은 값 바꾸기
draft.somewhere.deep.inside = 5;
})
콜백 함수 내부에서 값을 변경하면, 불변성 유지를 대신해주면서 새로운 상태를 생성해줌.
→ 그냥 코드를 작성하는데 알아서 불변성 관리를 해준다.
즉, 지금까지 state 변경할 때, 기존 배열이나 객체 스프레드 문법,concat으로 불러와서 새로운 객체 생성해서 불변성 유지하던 것을 쉽게 해준다.
// input 수정을 위한 함수
const onChange = useCallback(
(e) => {
const { name, value } = e.target;
// setForm({
// ...form,
// [name]: [value],
// });
**setForm(
produce(form, (draft) => {
draft[name] = value;
})
);**
},
[form]
);
// form 등록을 위한 함수
const onSubmit = useCallback(
(e) => {
e.preventDefault();
const info = {
id: nextId.current,
name: form.name,
username: form.username,
};
// array에 새 항목 등록
// setData({
// ...data,
// array: data.array.concat(info),
// });
setData(
produce(data, (draft) => {
draft.array.push(info);
})
);
//form 초기화
setForm({
name: "",
username: "",
});
nextId.current += 1;
},
[data, form.name, form.username]
);
const onRemove = useCallback(
(id) => {
// setData({
// ...data,
// array: data.array.filter((info) => info.id !== id),
// });
setData(
produce(data, (draft) => {
draft.array.splice(
draft.array.findIndex((info) => info.id === id),
1
);
})
);
},
[data]
);
그냥 기존의 state를 바꿔버리는 것 같지만, 불변성을 유지해준다.
그렇다고 해서 만능은 아니고 filter 같은 경우 오히려 produce사용하는 게 더 복잡하다.
useState의 함수형 업데이트와 immer 함께 쓰기
useState의 함수형 업데이트 전에 나왔는데, immer에서도 적용가능
produce 함수 호출할 때, 첫번째 파라미터가 함수 형태면, 업데이트 함수를 반환
const update = (draft => {
draft.value = 2;
});
const originalState = {
value: 1,
foo: 'bar',
};
const nextState = update(originalState);
console.log(nextState); // { value: 2, foo: 'bar' }
근데 애초에 produce안에 함수 형태가 들어가서, 업데이트 함수 형태로 사용하는게 state 빠진 느낌.
이를 이용해서 useState의 함수형 업데이트와 함께 사용할 수 있다.
setState함수안에 업데이트 함수를 반환해줘서 오히려 더 간결해짐.
const onChange = useCallback((e) => {
const { name, value } = e.target;
setForm(
produce((draft) => {
draft[name] = value;
})
);
}, []);
const onSubmit = useCallback(
(e) => {
e.preventDefault();
const info = {
id: nextId.current,
name: form.name,
username: form.username,
};
setData(
produce((draft) => {
draft.array.push(info);
})
);
setForm({
name: "",
username: "",
});
nextId.current += 1;
},
[form.name, form.username]
);